package com.topscomm.core.factory.minio;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import com.topscomm.basic.exception.ServiceException;
import com.topscomm.core.AbstractFileStoreService;
import com.topscomm.core.FileStoreTypeEnum;
import com.topscomm.core.IFileStoreService;
import com.topscomm.file.FileInfoDTO;
import com.topscomm.file.MinioConfig;
import com.topscomm.file.util.Utils;

import com.topscomm.pub.util.SecurityUtil;
import io.minio.GetObjectArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.ListPartsResponse;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.StatObjectArgs;
import io.minio.UploadObjectArgs;
import io.minio.http.Method;
import io.minio.messages.Part;
import lombok.extern.slf4j.Slf4j;

/**
 * minio文件实现
 *
 * @description:
 * @author: donghang
 * @date: 2021年3月13日下午10:39:31
 * @modify:
 */
@Slf4j
public class MinioFileStoreService extends AbstractFileStoreService implements IFileStoreService {
	private MinioFileClient minioClient;
	private MinioConfig minioConfig;
	/**
	 * 现在下载大小，避免内存溢出
	 */
	private int BUFF_SIZE = 2 << 20;

	public MinioFileStoreService(MinioConfig minioConfig) {
		super(FileStoreTypeEnum.MINIO.protocol);
		this.minioClient = minioFileClient(minioConfig);
		this.minioConfig = minioConfig;

	}

	/**
	 * 初始化分片的预签名的url
	 *
	 * @description:
	 * @param bucketName
	 * @param objectName
	 * @param totalPart
	 * @return
	 * @author: donghang
	 * @date: 2021年3月18日下午2:16:34
	 * @modify:
	 */
	public Map<String, Object> initMultiPartUpload(FileInfoDTO fileInfo) {
		Map<String, Object> result = new HashMap<>();
		try {
			String uploadId = this.getUpdateId(fileInfo);
			result.put("uploadId", uploadId);
			Map<String, String> partMap = new HashMap<>();
			Map<String, String> reqParams = new HashMap<>();
			reqParams.put("uploadId", uploadId);
			for (int i = 1; i <= fileInfo.getTotalBlockCount(); i++) {
				reqParams.put("partNumber", String.valueOf(i));
				String uploadUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
						.method(Method.PUT).bucket(fileInfo.getBucketName()).object(fileInfo.getRemoteFilePath())
						.expiry(7, TimeUnit.DAYS).extraQueryParams(reqParams).build());
				partMap.put(String.valueOf(i), uploadUrl);
			}
			result.put("uploadUrlsMap", partMap);
		} catch (Exception e) {
			log.error("error: {}", e.getMessage(), e);
			return null;
		}

		return result;
	}

	/**
	 * 合并
	 *
	 * @description:
	 * @param bucketName
	 * @param objectName
	 * @param uploadId
	 * @return
	 * @author: donghang
	 * @date: 2021年3月18日下午2:17:40
	 * @modify:
	 */
	public boolean mergeMultipartUpload(FileInfoDTO fileInfo, String uploadId) {
		try {
			Part[] parts = new Part[1000];
			ListPartsResponse partResult = minioClient.listMultipart(fileInfo.getBucketName(), null,
					fileInfo.getRemoteFilePath(), 1000, 0, uploadId, null, null);
			int partNumber = 1;
			for (Part part : partResult.result().partList()) {
				parts[partNumber - 1] = new Part(partNumber, part.etag());
				partNumber++;
			}
			minioClient.mergeMultipartUpload(fileInfo.getBucketName(), null, fileInfo.getRemoteFilePath(), uploadId,
					parts, null, null);
		} catch (Exception e) {
			log.error("minio服务器上传文件{}失败，异常{}", fileInfo.getRemoteFilePath(), e.getMessage());
			throw new ServiceException("上传失败，请重新上传");
		}
		return true;
	}

	/**
	 * 采用预签名方式上传
	 *
	 * @description:
	 * @param inputStream
	 * @param url
	 * @return
	 * @throws Exception
	 * @author: donghang
	 * @date: 2021年3月18日下午3:16:37
	 * @modify:
	 */
	@Override
	public String uploadPresigned(InputStream inputStream, String httpUrl) throws Exception {
		try {
			URL url = new URL(httpUrl);
			HttpURLConnection connection = (HttpURLConnection) url.openConnection();
			connection.setDoOutput(true);
			connection.setRequestMethod("PUT");
			OutputStream out = new DataOutputStream(connection.getOutputStream());
			int byteLen = 0;
			byte[] bufferOut = new byte[2048];
			while ((byteLen = inputStream.read(bufferOut)) != -1) {
				out.write(bufferOut, 0, byteLen);
			}
			out.flush();
			out.close();
			log.info("Http minio respose code:" + connection.getResponseCode());
		} catch (Exception e) {
			throw new RuntimeException("上传文件失败", e);
		}
		return null;
	}

	/**
	 * 通过路径上传
	 *
	 * @description:
	 * @param filePath
	 * @return
	 * @throws Exception
	 * @author: donghang
	 * @date: 2021年3月16日下午1:50:03
	 * @modify:
	 */
	@Override
	public String upload(FileInfoDTO fileInfo) throws Exception {
		this.upload(fileInfo.getBucketName(), fileInfo.getRemoteFilePath(), fileInfo.getLocateFilePath());
		return fileInfo.getRemoteFilePath();
	}

	/**
	 * 上传文件
	 *
	 * @description:
	 * @param inputStream
	 * @param fileName
	 *            桶/文件名
	 * @return
	 * @throws Exception
	 * @author: donghang
	 * @date: 2021年3月13日下午10:40:36
	 * @modify:
	 */
	@Override
	public String upload(InputStream inputStream, FileInfoDTO fileInfo) throws Exception {
		// 得到唯一对象名
		upload(fileInfo.getBucketName(), fileInfo.getRemoteFilePath(), fileInfo.getFileSize(), fileInfo.getBlockSize(),
				inputStream);
		return fileInfo.getRemoteFilePath();
	}

	/**
	 * 删除文件
	 *
	 * @description:
	 * @param objectName
	 * @author: donghang
	 * @date: 2021年3月13日下午10:41:47
	 * @modify:
	 */
	@Override
	public void delete(FileInfoDTO fileInfo) {
		try {
			String objectName = fileInfo.getRemoteFilePath();
			String bucketName = fileInfo.getBucketName();
			minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 下载文件
	 *
	 * @description:
	 * @param fileId
	 * @param outputStream
	 * @throws Exception
	 * @author: donghang
	 * @date: 2021年3月13日下午10:41:57
	 * @modify:
	 */
	@Override
	public InputStream downLoad(FileInfoDTO fileInfo) throws Exception {
		String objectName = fileInfo.getRemoteFilePath();
		String bucketName = fileInfo.getBucketName();
		return this.download(bucketName, objectName);
	}

	/**
	 * 下载文件到本地
	 *
	 * @description:
	 * @param fileInfo
	 * @param filePath
	 * @throws Exception
	 * @author: donghang
	 * @date: 2021年3月18日下午11:28:27
	 * @modify:
	 */
	@Override
	public void downLoad(FileInfoDTO fileInfo, String filePath) throws Exception {
		String objectName = fileInfo.getRemoteFilePath();
		String bucketName = fileInfo.getBucketName();
		this.download(bucketName, objectName, filePath);
	}

	/**
	 * 得到访问url
	 *
	 * @description:
	 * @param fileInfo
	 * @return
	 * @author: donghang
	 * @date: 2021年4月1日下午11:52:41
	 * @modify:
	 */
	@Override
	public String accessUrl(FileInfoDTO fileInfo) {
		// 默认为24小时
		int expireTime = fileInfo.getExpireTime() == 0 ? 24 : fileInfo.getExpireTime();
		TimeUnit timeUnit = null == fileInfo.getTimeUnit() ? TimeUnit.HOURS : fileInfo.getTimeUnit();
		return this.accessUrl(fileInfo.getBucketName(), fileInfo.getRemoteFilePath(), expireTime, timeUnit);
	}

	/**
	 *
	 * @description:
	 * @param bucketName
	 * @param objectName
	 * @return
	 * @author: donghang
	 * @date: 2021年5月19日上午12:55:48
	 * @modify:
	 */
	public boolean fileIsExist(FileInfoDTO fileInfo) {
		boolean exist = true;
		try {
			minioClient.statObject(StatObjectArgs.builder().bucket(fileInfo.getBucketName())
					.object(fileInfo.getRemoteFilePath()).build());
		} catch (Exception e) {
			exist = false;
		}
		return exist;
	}

	/**
	 *
	 * @description:
	 * @param objectName
	 * @param expire
	 * @param timeUnit
	 * @return
	 * @author: donghang
	 * @date: 2021年3月15日上午9:20:31
	 * @modify:
	 */
	public String accessUrl(String bucketName, String objectName, int expire, TimeUnit timeUnit) {
		try {
			Map<String, String> reqParams = new HashMap<>();
			if (objectName.substring(objectName.lastIndexOf(".")).toLowerCase().equals(".mp4")) {
				reqParams.put("content-type", "video/mpeg4");
			}
			return minioClient.getPresignedObjectUrl(
					GetPresignedObjectUrlArgs.builder().object(objectName).expiry(expire, timeUnit).method(Method.GET)
							.bucket(bucketName).extraQueryParams(reqParams).build());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * minio上传,通过文件路径上传
	 *
	 * @description:
	 * @param objectName
	 * @param file
	 * @author: donghang
	 * @date: 2021年3月13日下午10:42:16
	 * @modify:
	 */
	public void upload(String bucketName, String objectName, String file) {
		try {
			UploadObjectArgs.Builder builder = UploadObjectArgs.builder().bucket(bucketName).object(objectName)
					.filename(file);
			minioClient.uploadObject(builder.build());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 通过文件流上传
	 *
	 * @description:
	 * @param bucketName
	 * @param objectName
	 * @param fileStream
	 * @author: donghang
	 * @date: 2021年3月14日下午10:28:44
	 * @modify:
	 */
	private void upload(String bucketName, String objectName, long fileSize, long blockSize, InputStream fileStream) {
		try {
			PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucketName).object(objectName)
					.stream(fileStream, fileStream.available(), -1).build();
			minioClient.putObject(putObjectArgs);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 *
	 * @description:
	 * @param bucketName
	 * @param objectName
	 * @param localFile
	 * @author: donghang
	 * @date: 2021年3月18日下午2:03:54
	 * @modify:
	 */
	private void download(String bucketName, String objectName, String localFile) {
		FileOutputStream fileOutputStream = null;
		InputStream object = null;
		try {
			// 判断是否存在，先创建文件
			File file = new File(localFile);
			if (!file.getParentFile().exists()) {
				file.getParentFile().mkdirs();
			}
			if (!file.exists()) {
				Files.createFile(Paths.get(localFile));
			}
			object = download(bucketName, objectName);
			fileOutputStream = new FileOutputStream(localFile);
			Utils.copyStream(object, fileOutputStream, BUFF_SIZE);

		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			if (fileOutputStream != null) {
				try {
					fileOutputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (object != null) {
				try {
					object.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 *
	 * @description:
	 * @param bucketName
	 * @param objectName
	 * @param outputStream
	 * @author: donghang
	 * @date: 2021年3月18日下午2:04:42
	 * @modify:
	 */
	private InputStream download(String bucketName, String objectName) {
		try {
			GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucketName).object(objectName).build();
			return minioClient.getObject(getObjectArgs);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 构建minio客户端
	 *
	 * @description:
	 * @param minioConfig
	 * @return
	 * @author: donghang
	 * @date: 2021年3月18日下午2:04:47
	 * @modify:
	 */
	private MinioFileClient minioFileClient(MinioConfig minioConfig) {
		try {
			MinioClient.Builder minioClientBuilder = null;
			String accessKey = SecurityUtil.decryptDes(minioConfig.getAccessKey(), minioConfig.getDecryptKey());
			String secretKey = SecurityUtil.decryptDes(minioConfig.getSecretKey(), minioConfig.getDecryptKey());
			String endPoint = SecurityUtil.decryptDes(minioConfig.getEndpoint(), minioConfig.getDecryptKey());
			if (endPoint.contains("https")) {
				minioClientBuilder = MinioClient.builder().endpoint(endPoint, 443, true)
						.credentials(accessKey, secretKey);
			} else {
				minioClientBuilder = MinioClient.builder().endpoint(endPoint)
						.credentials(accessKey, secretKey);
			}
			if (Utils.isNotEmpty(minioConfig.getRegion())) {
				// minioClientBuilder.region(minioConfig.getRegion());
			}
			return new MinioFileClient(minioClientBuilder.build());
		} catch (Exception e) {
			throw new RuntimeException("配置初始化失败", e);
		}

	}

	/**
	 * 得到预签名的updateID
	 *
	 * @description:
	 * @param fileInfo
	 * @return
	 * @author: donghang
	 * @date: 2021年3月18日下午3:37:24
	 * @modify:
	 */
	@Override
	public String getUpdateId(FileInfoDTO fileInfo) {
		String uploadId;
		try {
			uploadId = minioClient.initMultiPartUpload(fileInfo.getBucketName(), null, fileInfo.getRemoteFilePath(),
					null, null);
			return uploadId;
		} catch (Exception e) {
			throw new RuntimeException("文件服务器连接失败", e);
		}

	}

}
